const CONSTANTS = {
    CHAR_UPPERCASE_A: 65, /* A */
    CHAR_LOWERCASE_A: 97, /* a */
    CHAR_UPPERCASE_Z: 90, /* Z */
    CHAR_LOWERCASE_Z: 122, /* z */

    CHAR_DOT: 46, /* . */
    CHAR_FORWARD_SLASH: 47, /* / */
    CHAR_BACKWARD_SLASH: 92, /* \ */
    CHAR_COLON: 58, /* : */
}

const sep = '/';
const delimiter = ':';
const win32 = null;
const posix = null;

/**
 * @param {string} path
 * @param {string} [ext]
 * @returns {string}
 */
function basename(path, ext) {
    if (ext !== undefined)
    validateString(ext, 'ext');
    validateString(path, 'path');
    let start = 0;
    let end = -1;
    let matchedSlash = true;

    if (path.length >= 2 &&
    isWindowsDeviceRoot(path.charCodeAt(0)) &&
    path.charCodeAt(1) === CONSTANTS.CHAR_COLON) {
        start = 2;
    }

    if (ext !== undefined && ext.length > 0 && ext.length <= path.length) {
        if (ext === path)
        return '';
        let extIdx = ext.length - 1;
        let firstNonSlashEnd = -1;
        for (let i = path.length - 1; i >= start; --i) {
            const code = path.charCodeAt(i);
            if (isPathSeparator(code)) {
                if (!matchedSlash) {
                    start = i + 1;
                    break;
                }
            } else {
                if (firstNonSlashEnd === -1) {
                    matchedSlash = false;
                    firstNonSlashEnd = i + 1;
                }
                if (extIdx >= 0) {
                    if (code === ext.charCodeAt(extIdx)) {
                        if (--extIdx === -1) {
                            end = i;
                        }
                    } else {
                        extIdx = -1;
                        end = firstNonSlashEnd;
                    }
                }
            }
        }

        if (start === end)
        end = firstNonSlashEnd;
        else if (end === -1)
        end = path.length;
        return path.slice(start, end);
    }
    for (let i = path.length - 1; i >= start; --i) {
        if (isPathSeparator(path.charCodeAt(i))) {
            if (!matchedSlash) {
                start = i + 1;
                break;
            }
        } else if (end === -1) {
            matchedSlash = false;
            end = i + 1;
        }
    }

    if (end === -1)
    return '';
    return path.slice(start, end);
}

/**
 * @param {string} path
 * @returns {string}
 */
function dirname(path) {
    validateString(path, 'path');
    const len = path.length;
    if (len === 0)
    return '.';
    let rootEnd = -1;
    let offset = 0;
    const code = path.charCodeAt(0);

    if (len === 1) {
        return isPathSeparator(code) ? path : '.';
    }

    if (isPathSeparator(code)) {
        rootEnd = offset = 1;

        if (isPathSeparator(path.charCodeAt(1))) {
            let j = 2;
            let last = j;

            while (j < len &&
            !isPathSeparator(path.charCodeAt(j))) {
                j++;
            }
            if (j < len && j !== last) {
                last = j;
                while (j < len &&
                isPathSeparator(path.charCodeAt(j))) {
                    j++;
                }
                if (j < len && j !== last) {
                    last = j;
                    while (j < len &&
                    !isPathSeparator(path.charCodeAt(j))) {
                        j++;
                    }
                    if (j === len) {
                        return path;
                    }
                    if (j !== last) {
                        rootEnd = offset = j + 1;
                    }
                }
            }
        }
    } else if (isWindowsDeviceRoot(code) &&
    path.charCodeAt(1) === CONSTANTS.CHAR_COLON) {
        rootEnd =
            len > 2 && isPathSeparator(path.charCodeAt(2)) ? 3 : 2;
        offset = rootEnd;
    }

    let end = -1;
    let matchedSlash = true;
    for (let i = len - 1; i >= offset; --i) {
        if (isPathSeparator(path.charCodeAt(i))) {
            if (!matchedSlash) {
                end = i;
                break;
            }
        } else {
            matchedSlash = false;
        }
    }

    if (end === -1) {
        if (rootEnd === -1)
        return '.';

        end = rootEnd;
    }
    return path.slice(0, end);
}

/**
 * @param {string} path
 * @returns {string}
 */
function extname(path) {
    validateString(path, 'path');
    let start = 0;
    let startDot = -1;
    let startPart = 0;
    let end = -1;
    let matchedSlash = true;
    let preDotState = 0;

    if (path.length >= 2 &&
    path.charCodeAt(1) === CONSTANTS.CHAR_COLON &&
    isWindowsDeviceRoot(path.charCodeAt(0))) {
        start = startPart = 2;
    }

    for (let i = path.length - 1; i >= start; --i) {
        const code = path.charCodeAt(i);
        if (isPathSeparator(code)) {
            if (!matchedSlash) {
                startPart = i + 1;
                break;
            }
            continue;
        }
        if (end === -1) {
            matchedSlash = false;
            end = i + 1;
        }
        if (code === CONSTANTS.CHAR_DOT) {
            if (startDot === -1)
            startDot = i;
            else if (preDotState !== 1)
            preDotState = 1;
        } else if (startDot !== -1) {
            preDotState = -1;
        }
    }

    if (startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 &&
    startDot === end - 1 &&
    startDot === startPart + 1)) {
        return '';
    }
    return path.slice(startDot, end);
}

/**
 * @param {{
 *  dir?: string;
 *  root?: string;
 *  base?: string;
 *  name?: string;
 *  ext?: string;
 *  }} pathObject
 * @returns {string}
 */
function format(pathObject) {
    //此处的分隔符在win32平台是'\\',在POSIX标准中是'/'
    var sep = '/';
    validateObject(pathObject, 'pathObject');
    const dir = pathObject.dir || pathObject.root;
    const base = pathObject.base ||
    `${pathObject.name || ''}${pathObject.ext || ''}`;
    if (!dir) {
        return base;
    }
    return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`;
}

/**
 * @param {string} path
 * @returns {boolean}
 */
function isAbsolute(path) {
    validateString(path, 'path');
    var isWin32 = false;
    if (isWin32) {
        return path.length > 0 &&
        path.charCodeAt(0) === CONSTANTS.CHAR_FORWARD_SLASH
    } else {
        const len = path.length;
        if (len === 0)
        return false;

        const code = path.charCodeAt(0);
        return isPathSeparator(code) ||
        (len > 2 && isWindowsDeviceRoot(code) &&
        path.charCodeAt(1) === CONSTANTS.CHAR_COLON &&
        isPathSeparator(path.charCodeAt(2)));
    }
}

/**
 * @param {...string} args
 * @returns {string}
 */
function join(...args) {
    if (args.length === 0)
    return '.';
    let joined;
    for (let i = 0; i < args.length; ++i) {
        const arg = args[i];
        validateString(arg, 'path');
        if (arg.length > 0) {
            if (joined === undefined)
            joined = arg;
            else
            joined += `/${arg}`;
        }
    }
    if (joined === undefined)
    return '.';
    return normalize(joined);
}

/**
 * @param {string} path
 * @returns {string}
 */
function normalize(path) {
    validateString(path, 'path');

    if (path.length === 0)
    return '.';

    const isAbsolute =
        path.charCodeAt(0) === CONSTANTS.CHAR_FORWARD_SLASH;
    const trailingSeparator =
        path.charCodeAt(path.length - 1) === CONSTANTS.CHAR_FORWARD_SLASH;

    path = normalizeString(path,!isAbsolute, '/', isPosixPathSeparator);

    if (path.length === 0) {
        if (isAbsolute)
        return '/';
        return trailingSeparator ? './' : '.';
    }
    if (trailingSeparator)
    path += '/';

    return isAbsolute ? `/${path}` : path;
}

/**
 * @param {string} path
 * @returns {{
 *   dir: string;
 *   root: string;
 *   base: string;
 *   name: string;
 *   ext: string;
 *   }}
 */
function parse(path) {
    validateString(path, 'path');

    const ret = {
        root: '',
        dir: '',
        base: '',
        ext: '',
        name: ''
    };
    if (path.length === 0)
    return ret;
    const isAbsolute =
        path.charCodeAt(0) === CONSTANTS.CHAR_FORWARD_SLASH;
    let start;
    if (isAbsolute) {
        ret.root = '/';
        start = 1;
    } else {
        start = 0;
    }
    let startDot = -1;
    let startPart = 0;
    let end = -1;
    let matchedSlash = true;
    let i = path.length - 1;

    let preDotState = 0;

    for (; i >= start; --i) {
        const code = path.charCodeAt(i);
        if (code === CONSTANTS.CHAR_FORWARD_SLASH) {
            if (!matchedSlash) {
                startPart = i + 1;
                break;
            }
            continue;
        }
        if (end === -1) {
            matchedSlash = false;
            end = i + 1;
        }
        if (code === CONSTANTS.CHAR_DOT) {
            if (startDot === -1)
            startDot = i;
            else if (preDotState !== 1)
            preDotState = 1;
        } else if (startDot !== -1) {
            preDotState = -1;
        }
    }

    if (end !== -1) {
        const start = startPart === 0 && isAbsolute ? 1 : startPart;
        if (startDot === -1 ||
        preDotState === 0 ||
        (preDotState === 1 &&
        startDot === end - 1 &&
        startDot === startPart + 1)) {
            ret.base = ret.name = path.slice(start, end);
        } else {
            ret.name = path.slice(start, startDot);
            ret.base = path.slice(start, end);
            ret.ext = path.slice(startDot, end);
        }
    }

    if (startPart > 0)
    ret.dir = path.slice(0, startPart - 1);
    else if (isAbsolute)
    ret.dir = '/';

    return ret;
}

/**
 * @param {string} from
 * @param {string} to
 * @returns {string}
 */
function relative(from, to) {
    validateString(from, 'from');
    validateString(to, 'to');

    if (from === to)
    return '';

    from = resolve(from);
    to = resolve(to);

    if (from === to)
    return '';

    const fromStart = 1;
    const fromEnd = from.length;
    const fromLen = fromEnd - fromStart;
    const toStart = 1;
    const toLen = to.length - toStart;

    const length = (fromLen < toLen ? fromLen : toLen);
    let lastCommonSep = -1;
    let i = 0;
    for (; i < length; i++) {
        const fromCode = from.charCodeAt(fromStart + i);
        if (fromCode !== to.charCodeAt(toStart + i))
        break;
        else if (fromCode === CONSTANTS.CHAR_FORWARD_SLASH)
        lastCommonSep = i;
    }
    if (i === length) {
        if (toLen > length) {
            if (to.charCodeAt(toStart + i) === CONSTANTS.CHAR_FORWARD_SLASH) {
                return to.slice(toStart + i + 1);
            }
            if (i === 0) {
                return to.slice(toStart + i);
            }
        } else if (fromLen > length) {
            if (from.charCodeAt(fromStart + i) ===
            CONSTANTS.CHAR_FORWARD_SLASH) {
                lastCommonSep = i;
            } else if (i === 0) {
                lastCommonSep = 0;
            }
        }
    }

    let out = '';
    for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
        if (i === fromEnd ||
        from.charCodeAt(i) === CONSTANTS.CHAR_FORWARD_SLASH) {
            out += out.length === 0 ? '..' : '/..';
        }
    }

    return `${out}${to.slice(toStart + lastCommonSep)}`;
}

/**
 * path.resolve([from ...], to)
 * @param {...string} args
 * @returns {string}
 */
function resolve(...args) {
    let resolvedPath = '';
    let resolvedAbsolute = false;

    for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
        const path = i >= 0 ? args[i] : '';

        validateString(path, 'path');

        if (path.length === 0) {
            continue;
        }

        resolvedPath = `${path}/${resolvedPath}`;
        resolvedAbsolute =
        path.charCodeAt(0) === CONSTANTS.CHAR_FORWARD_SLASH;
    }

    // Normalize the path
    resolvedPath = normalizeString(resolvedPath,!resolvedAbsolute, '/',
        isPosixPathSeparator);

    if (resolvedAbsolute) {
        return `/${resolvedPath}`;
    }
    return resolvedPath.length > 0 ? resolvedPath : '.';
}

/**
 * @param {string} path
 * @returns {string}
 */
function toNamespacedPath(path) {
    return path;
}

//----------------------------------
//内部函数 Start
function isWindowsDeviceRoot(code) {
    return (code >= CONSTANTS.CHAR_UPPERCASE_A && code <= CONSTANTS.CHAR_UPPERCASE_Z) ||
    (code >= CONSTANTS.CHAR_LOWERCASE_A && code <= CONSTANTS.CHAR_LOWERCASE_Z);
}

function isPathSeparator(code) {
    return code === CONSTANTS.CHAR_FORWARD_SLASH || code === CONSTANTS.CHAR_BACKWARD_SLASH;
}

function isPosixPathSeparator(code) {
    return code === CONSTANTS.CHAR_FORWARD_SLASH;
}

function validateString(value, name) {
    if (typeof value !== 'string') {
        throw new Error('TypeError, ' + name + ' must be a string. Received:' + JSON.stringify(value));
    }
}

function validateObject(value, name, options) {
    const allowArray = getOwnPropertyValueOrDefault(options, 'allowArray', false);
    const allowFunction = getOwnPropertyValueOrDefault(options, 'allowFunction', false);
    const nullable = getOwnPropertyValueOrDefault(options, 'nullable', false);
    if ((!nullable && value === null) ||
    (!allowArray && Array.isArray(value)) ||
    (typeof value !== 'object' && (
        !allowFunction || typeof value !== 'function'
    ))) {
        throw new Error('ERR_INVALID_ARG_TYPE, name:' + name + ',value:' + value);
    }
}

function getOwnPropertyValueOrDefault(options, key, defaultValue) {
    return options == null || !options.hasOwnProperty(key) ?
        defaultValue :
    options[key];
}

function normalizeString(path, allowAboveRoot, separator, isPathSeparator) {
    let res = '';
    let lastSegmentLength = 0;
    let lastSlash = -1;
    let dots = 0;
    let code = 0;
    for (let i = 0; i <= path.length; ++i) {
        if (i < path.length)
        code = path.charCodeAt(i);
        else if (isPathSeparator(code))
        break;
        else
        code = CONSTANTS.CHAR_FORWARD_SLASH;

        if (isPathSeparator(code)) {
            if (lastSlash === i - 1 || dots === 1) {
                // NOOP
            } else if (dots === 2) {
                if (res.length < 2 || lastSegmentLength !== 2 ||
                res.charCodeAt(res.length - 1) !== CONSTANTS.CHAR_DOT ||
                res.charCodeAt(res.length - 2) !== CONSTANTS.CHAR_DOT) {
                    if (res.length > 2) {
                        const lastSlashIndex = res.lastIndexOf(separator);
                        if (lastSlashIndex === -1) {
                            res = '';
                            lastSegmentLength = 0;
                        } else {
                            res = res.slice(0, lastSlashIndex);
                            lastSegmentLength =
                            res.length - 1 - res.lastIndexOf(separator);
                        }
                        lastSlash = i;
                        dots = 0;
                        continue;
                    } else if (res.length !== 0) {
                        res = '';
                        lastSegmentLength = 0;
                        lastSlash = i;
                        dots = 0;
                        continue;
                    }
                }
                if (allowAboveRoot) {
                    res += res.length > 0 ? `${separator}..` : '..';
                    lastSegmentLength = 2;
                }
            } else {
                if (res.length > 0)
                res += `${separator}${path.slice(lastSlash + 1, i)}`;
                else
                res = path.slice(lastSlash + 1, i);
                lastSegmentLength = i - lastSlash - 1;
            }
            lastSlash = i;
            dots = 0;
        } else if (code === CONSTANTS.CHAR_DOT && dots !== -1) {
            ++dots;
        } else {
            dots = -1;
        }
    }
    return res;
}

//内部函数 End

export const path = {
    sep,
    delimiter,
    win32,
    posix,
    basename,
    dirname,
    extname,
    format,
    isAbsolute,
    join,
    normalize,
    parse,
    relative,
    resolve,
    toNamespacedPath
}